//----------------------------------------------------------------------------
//  ,-.
// /_\___ ,   Ninja Framework
// '  ___\    (C) Riccardo Capra - http://riccar.do
//  //   \\
//----------------------------------------------------------------------------
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
//----------------------------------------------------------------------------

#include "NjExample01.h"
#include "NjRWLockTest.h"
#include "NjSynchronizationTest.h"
#include "NjSharedSyncObjectTest.h"
#include "NjDebugSystemTestImplementation.h"
#include "NjSharedSyncObjectConstants.h"
#include "NjThreadsSystem.h"
#include "NjMemUtils.h"
#include "NjRandUint.h"
#include "NjExampleRandGenerator.h"
#include "NjTestUnit.h"

//----------------------------------------------------------------------------
class NjRWLockTest::Static
{
public:
	static void RunRWLockFunctionalMultithreaded();
	static void RunRWLockAssert();
	static void RunRWLockFunctionalThread(void * pData);
	static void RunRWLockAssertThread(void * pData);

	enum ErrorState
	{
		STATUS_SUCCESS,
		NOT_INITIALIZED
	};

	class RWLockFunctionalSharedData
	{
	public:
		NJ_TRIVIAL_DEFAULT_CONSTRUCTOR(RWLockFunctionalSharedData);
		NJ_TRIVIAL_DESTRUCTOR(RWLockFunctionalSharedData);

		static const NjUint TOT_THREADS = 20;
		static const NjUint TOT_RWLOCKS = 5;
		static const NjUint ITERATIONS = 100*NjSharedSyncObjectConstants::ITERATIONS_FACTOR;;
		static const NjUint TOT_ENTRIES = ITERATIONS*TOT_THREADS*4;

		class ThreadData
		{
		public:
			NJ_TRIVIAL_DEFAULT_CONSTRUCTOR(ThreadData);
			NJ_TRIVIAL_DESTRUCTOR(ThreadData);

			RWLockFunctionalSharedData * m_pSharedData;
			NjThreadsSystem::Thread * m_pThread;
			ErrorState m_eErrorState;

			NJ_DISABLE_COPY(ThreadData);
		};

		NjRWLock m_aRWLock[TOT_RWLOCKS];
		ThreadData m_aThreadsData[TOT_THREADS];

		enum LockState
		{
			TRY_LOCK_READ_START,
			LOCK_READ_START,
			LOCK_READ_END,
			LOCK_READ_FAIL,
			UNLOCK_READ_START,
			UNLOCK_READ_END,
			TRY_LOCK_WRITE_START,
			LOCK_WRITE_START,
			LOCK_WRITE_END,
			LOCK_WRITE_FAIL,
			UNLOCK_WRITE_START,
			UNLOCK_WRITE_END
		};

		class Entry
		{
		public:
			NJ_TRIVIAL_DEFAULT_CONSTRUCTOR(Entry);
			NJ_TRIVIAL_DESTRUCTOR(Entry);

			NjUint m_uRWLock;
			LockState m_eLockState;
#if NJ_SYNCHRONIZATION_TEST_SNAPSHOT_SYNC_OBJECT
			NjUint8 m_aSnapshot[sizeof(NjRWLock)];
#endif // NJ_SYNCHRONIZATION_TEST_SNAPSHOT_SYNC_OBJECT
			ThreadData * m_pThreadData;

			NJ_DISABLE_COPY(Entry);
		};

		Entry m_aEntries[TOT_ENTRIES];
		NjAtomic<NjUint> m_kTotEntries;

		NJ_DISABLE_COPY(RWLockFunctionalSharedData);
	};

	NJ_DISABLE_INSTANCE(Static);
};
//----------------------------------------------------------------------------
void NjRWLockTest::Run(void)
{
	NjSharedSyncObjectTest<NjRWLock, NjRWLock::Read>::RunFunctional();
	NjSharedSyncObjectTest<NjRWLock, NjRWLock::Read>::RunPerformances();
	NjSharedSyncObjectTest<NjRWLock, NjRWLock::Read>::RunLocker();

	NjSharedSyncObjectTest<NjRWLock, NjRWLock::Write>::RunFunctional();
	NjSharedSyncObjectTest<NjRWLock, NjRWLock::Write>::RunFunctionalMultithreaded();
	NjSharedSyncObjectTest<NjRWLock, NjRWLock::Write>::RunPerformances();
	NjSharedSyncObjectTest<NjRWLock, NjRWLock::Write>::RunLocker();

	Static::RunRWLockFunctionalMultithreaded();
	Static::RunRWLockAssert();
}
//----------------------------------------------------------------------------
void NjRWLockTest::Static::RunRWLockFunctionalMultithreaded()
{
	NJ_TEST_UNITS_GROUP(NjSharedSyncObjectConstants::Query<NjRWLock>::ms_szSyncObject);
		NJ_TEST_UNIT("Constructors, Destructors, Lock, TryLock, Unlock (multi threaded)");
			RWLockFunctionalSharedData * const pSharedData(NjNew(RWLockFunctionalSharedData));

			pSharedData->m_kTotEntries.Set(0);

			RWLockFunctionalSharedData::ThreadData * pPos(pSharedData->m_aThreadsData);
			RWLockFunctionalSharedData::ThreadData * const pEnd(pPos+RWLockFunctionalSharedData::TOT_THREADS);

			while (pPos!=pEnd)
			{
				RWLockFunctionalSharedData::ThreadData & kThreadData(*pPos);
				++pPos;

				kThreadData.m_pSharedData=pSharedData;
				kThreadData.m_pThread=NjThreadsSystem::CreateThread(RunRWLockFunctionalThread, &kThreadData);

				kThreadData.m_eErrorState=NOT_INITIALIZED;

				NJ_TEST(kThreadData.m_pThread!=NJ_NULL);
			}

			pPos=pSharedData->m_aThreadsData;

			while (pPos!=pEnd)
			{
				RWLockFunctionalSharedData::ThreadData & kThreadData(*pPos);
				++pPos;

				NJ_TEST(NjThreadsSystem::TryJoinThread(kThreadData.m_pThread, (pEnd+1-pPos)*RWLockFunctionalSharedData::ITERATIONS*NjSharedSyncObjectConstants::TIME_GRANULARITY)==NjEventsSystem::TRY_SUCCESS);
				NJ_TEST(NjThreadsSystem::DestroyThread(kThreadData.m_pThread));
				NJ_TEST(kThreadData.m_eErrorState==STATUS_SUCCESS);
			}

			RWLockFunctionalSharedData::Entry const * pEntry(pSharedData->m_aEntries);
			RWLockFunctionalSharedData::Entry const * const pEntryEnd(pEntry+pSharedData->m_kTotEntries.Get());

			NjRWLock * aOwnedRWLock[RWLockFunctionalSharedData::TOT_THREADS];
			NjMemUtils::Zero(aOwnedRWLock, sizeof(aOwnedRWLock));

			NjInt aRwLockState[RWLockFunctionalSharedData::TOT_RWLOCKS];
			NjMemUtils::Zero(aRwLockState, sizeof(aRwLockState));

			NjUint uTotReads(0);
			NjUint uTotWrites(0);

			while (pEntry<pEntryEnd)
			{
				NjUint const uThread(pEntry->m_pThreadData-pSharedData->m_aThreadsData);
				NJ_TEST(uThread<RWLockFunctionalSharedData::TOT_THREADS);

				NjUint const uRWLock(pEntry->m_uRWLock);
				NJ_TEST(uRWLock<RWLockFunctionalSharedData::TOT_RWLOCKS);

				NjRWLock * const pRWLock(pSharedData->m_aRWLock+uRWLock);

				switch(pEntry->m_eLockState)
				{
					case RWLockFunctionalSharedData::TRY_LOCK_READ_START:
					case RWLockFunctionalSharedData::LOCK_READ_START:
						NJ_TEST(aOwnedRWLock[uThread]==NJ_NULL);
						aOwnedRWLock[uThread]=pRWLock;
						break;

					case RWLockFunctionalSharedData::LOCK_READ_END:
						NJ_TEST(aOwnedRWLock[uThread]==pRWLock);
						NJ_TEST(aRwLockState[uRWLock]>=0);
						++aRwLockState[uRWLock];
						++uTotReads;
						break;

					case RWLockFunctionalSharedData::UNLOCK_READ_END:
					case RWLockFunctionalSharedData::LOCK_READ_FAIL:
						NJ_TEST(aOwnedRWLock[uThread]==pRWLock);
						aOwnedRWLock[uThread]=NJ_NULL;
						break;

					case RWLockFunctionalSharedData::UNLOCK_READ_START:
						NJ_TEST(aOwnedRWLock[uThread]==pRWLock);
						NJ_TEST(aRwLockState[uRWLock]>0);
						--aRwLockState[uRWLock];
						break;

					case RWLockFunctionalSharedData::TRY_LOCK_WRITE_START:
					case RWLockFunctionalSharedData::LOCK_WRITE_START:
						NJ_TEST(aOwnedRWLock[uThread]==NJ_NULL);
						aOwnedRWLock[uThread]=pRWLock;
						break;

					case RWLockFunctionalSharedData::LOCK_WRITE_END:
						NJ_TEST(aOwnedRWLock[uThread]==pRWLock);
						NJ_TEST(aRwLockState[uRWLock]==0);
						aRwLockState[uRWLock]=-1;
						++uTotWrites;
						break;

					case RWLockFunctionalSharedData::UNLOCK_WRITE_END:
					case RWLockFunctionalSharedData::LOCK_WRITE_FAIL:
						NJ_TEST(aOwnedRWLock[uThread]==pRWLock);
						aOwnedRWLock[uThread]=NJ_NULL;
						break;

					case RWLockFunctionalSharedData::UNLOCK_WRITE_START:
						NJ_TEST(aOwnedRWLock[uThread]==pRWLock);
						NJ_TEST(aRwLockState[uRWLock]==-1);
						aRwLockState[uRWLock]=0;
						break;

					default:
						NJ_TEST_FAIL();
				}
				++pEntry;
			}

#if NJ_TEST_PERFORMANCE_RECORDING_SYSTEM_ENABLED
			CaFloat const fRWRatio(CaFloat(uTotReads)/CaFloat(uTotWrites));

			CaTestPerformanceRecordingSystem::Group * const pGroup(CaTestPerformanceRecordingSystem::OpenGroup("NjRWLock"));
			CaTestPerformanceRecordingSystem::Group * const pSubGroup(CaTestPerformanceRecordingSystem::OpenSubGroup(pGroup, "Performances"));
			CaTestPerformanceRecordingSystem::Table * const pTable(CaTestPerformanceRecordingSystem::OpenTable(pSubGroup, "LockRead, LockWrite"));
			CaTestPerformanceRecordingSystem::Column * const pColumnRatio(CaTestPerformanceRecordingSystem::OpenColumn(pTable, "Test ratio", CaTestPerformanceRecordingSystem::TYPE_FLOAT));
			CaTestPerformanceRecordingSystem::Row * const pRow(CaTestPerformanceRecordingSystem::CreateRow(pTable));
			CaTestPerformanceRecordingSystem::SetValue(pRow, pColumnRatio, fRWRatio);
			CaTestPerformanceRecordingSystem::CloseRow(pRow);
			CaTestPerformanceRecordingSystem::CloseColumn(pColumnRatio);
			CaTestPerformanceRecordingSystem::CloseTable(pTable);
			CaTestPerformanceRecordingSystem::CloseGroup(pSubGroup);
			CaTestPerformanceRecordingSystem::CloseGroup(pGroup);
#endif // NJ_TEST_PERFORMANCE_RECORDING_SYSTEM_ENABLED

			NjDelete(pSharedData);
		NJ_TEST_UNIT_END();
	NJ_TEST_UNITS_GROUP_END();
}
//----------------------------------------------------------------------------
void NjRWLockTest::Static::RunRWLockAssert()
{
#if NJ_RW_LOCK_STORE_USERS_ENABLED && NJ_DEBUG_SYSTEM_ENABLED && NJ_ASSERT_ENABLED
	NJ_TEST_UNITS_GROUP(NjSharedSyncObjectConstants::Query<NjRWLock>::ms_szSyncObject);
		NJ_TEST_UNIT("Assertion (destruction)");
			NjSmartPtr<NjDebugSystem::Implementation> spImplementation(NjDebugSystem::GetImplementation());
			NjDebugSystemTestImplementation * const pTestBreakpoint(NjNew(NjDebugSystemTestImplementation(spImplementation)));
			NJ_TEST(pTestBreakpoint!=NJ_NULL);
			NjDebugSystem::SetImplementation(pTestBreakpoint);

			NjThreadsSystem::Thread * pThread;

			{
				NjRWLock kSyncObject;
				kSyncObject.LockWrite();
				NJ_TEST(!pTestBreakpoint->WasTriggeredBreakpoint());

				pThread=NjThreadsSystem::CreateThread(RunRWLockAssertThread, &kSyncObject);

				NJ_TEST(pThread!=NJ_NULL);
				NJ_TEST(NjThreadsSystem::Sleep(NjSharedSyncObjectConstants::TIME_GRANULARITY));
			}

			NJ_TEST(pTestBreakpoint->WasTriggeredBreakpoint());

			NJ_TEST(NjThreadsSystem::TryJoinThread(pThread, NjSharedSyncObjectConstants::TIME_GRANULARITY*5)==NjEventsSystem::TRY_SUCCESS);
			NJ_TEST(NjThreadsSystem::DestroyThread(pThread));

			NjDebugSystem::SetImplementation(spImplementation);
		NJ_TEST_UNIT_END();
	NJ_TEST_UNITS_GROUP_END();
#endif // NJ_RW_LOCK_STORE_USERS_ENABLED && NJ_DEBUG_SYSTEM_ENABLED && NJ_ASSERT_ENABLED

#if NJ_RW_LOCK_STORE_WRITE_LOCKER_ENABLED && NJ_DEBUG_SYSTEM_ENABLED && NJ_ASSERT_ENABLED
	NJ_TEST_UNITS_GROUP(NjSharedSyncObjectConstants::Query<NjRWLock>::ms_szSyncObject);
		NJ_TEST_UNIT("Assertion (destruction)");
			NjSmartPtr<NjDebugSystem::Implementation> spImplementation(NjDebugSystem::GetImplementation());
			NjDebugSystemTestImplementation * const pTestBreakpoint(NjNew(NjDebugSystemTestImplementation(spImplementation)));
			NJ_TEST(pTestBreakpoint!=NJ_NULL);
			NjDebugSystem::SetImplementation(pTestBreakpoint);

			{
				NjRWLock kSyncObject;
				kSyncObject.LockWrite();
				NJ_TEST(!pTestBreakpoint->WasTriggeredBreakpoint());
			}

			NJ_TEST(pTestBreakpoint->WasTriggeredBreakpoint());

			NjDebugSystem::SetImplementation(spImplementation);
		NJ_TEST_UNIT_END();

		NJ_TEST_UNIT("Assertion (double lock)");
			NjSmartPtr<NjDebugSystem::Implementation> spImplementation(NjDebugSystem::GetImplementation());
			NjDebugSystemTestImplementation * const pTestBreakpoint(NjNew(NjDebugSystemTestImplementation(spImplementation)));
			NJ_TEST(pTestBreakpoint!=NJ_NULL);
			NjDebugSystem::SetImplementation(pTestBreakpoint);

			{
				NjRWLock kSyncObject;
				kSyncObject.LockWrite();
				NJ_TEST(!pTestBreakpoint->WasTriggeredBreakpoint());
				kSyncObject.LockWrite();
				NJ_TEST(pTestBreakpoint->WasTriggeredBreakpoint());
				kSyncObject.UnlockWrite();
			}

			NjDebugSystem::SetImplementation(spImplementation);
		NJ_TEST_UNIT_END();
	NJ_TEST_UNITS_GROUP_END();
#endif // NJ_RW_LOCK_STORE_WRITE_LOCKER_ENABLED && NJ_DEBUG_SYSTEM_ENABLED && NJ_ASSERT_ENABLED
}
//----------------------------------------------------------------------------
void NjRWLockTest::Static::RunRWLockFunctionalThread(void * pData)
{
	RWLockFunctionalSharedData::ThreadData & kThreadData(*reinterpret_cast<RWLockFunctionalSharedData::ThreadData *>(pData));
	RWLockFunctionalSharedData & kSharedData(*kThreadData.m_pSharedData);
	NjRandUint<EXAMPLE_RAND_GENERATOR> kRand(0, 1000000);

	NjUint uIterations(RWLockFunctionalSharedData::ITERATIONS);

	while (uIterations>0)
	{
		--uIterations;

		NjUint const uRWLock(kRand()%RWLockFunctionalSharedData::TOT_RWLOCKS);
		NjUint uAction(kRand()%4);

		NjRWLock * const pRWLock(kSharedData.m_aRWLock+uRWLock);

		NjUint uIndex(kSharedData.m_kTotEntries.PostIncrement());
		RWLockFunctionalSharedData::Entry * pEntry(kSharedData.m_aEntries+uIndex);
		pEntry->m_uRWLock=uRWLock;
		pEntry->m_pThreadData=&kThreadData;
#if NJ_SYNCHRONIZATION_TEST_SNAPSHOT_SYNC_OBJECT
		NjMemUtils::Copy(pEntry->m_aSnapshot, sizeof(pEntry->m_aSnapshot), pRWLock, sizeof(NjRWLock));
#endif // NJ_SYNCHRONIZATION_TEST_SNAPSHOT_SYNC_OBJECT

		NjBool bLockSuccess(NJ_TRUE);
		RWLockFunctionalSharedData::LockState eNextState;
		if (uAction<=1)
		{
			if (uAction==0)
			{
				pEntry->m_eLockState=RWLockFunctionalSharedData::LOCK_WRITE_START;
				pRWLock->LockWrite();
			}
			else
			{
				pEntry->m_eLockState=RWLockFunctionalSharedData::TRY_LOCK_WRITE_START;
				bLockSuccess=pRWLock->TryLockWrite(NjSharedSyncObjectConstants::TIME_GRANULARITY);
			}

			if (bLockSuccess)
				eNextState=RWLockFunctionalSharedData::LOCK_WRITE_END;
			else
				eNextState=RWLockFunctionalSharedData::LOCK_WRITE_FAIL;
		}
		else
		{
			if (uAction==2)
			{
				pEntry->m_eLockState=RWLockFunctionalSharedData::LOCK_READ_START;
				pRWLock->LockRead();
			}
			else
			{
				pEntry->m_eLockState=RWLockFunctionalSharedData::TRY_LOCK_READ_START;
				bLockSuccess=pRWLock->TryLockRead(NjSharedSyncObjectConstants::TIME_GRANULARITY);
			}

			if (bLockSuccess)
				eNextState=RWLockFunctionalSharedData::LOCK_READ_END;
			else
				eNextState=RWLockFunctionalSharedData::LOCK_READ_FAIL;
		}

		uIndex=kSharedData.m_kTotEntries.PostIncrement();
		pEntry=kSharedData.m_aEntries+uIndex;
		pEntry->m_uRWLock=uRWLock;
		pEntry->m_pThreadData=&kThreadData;
		pEntry->m_eLockState=eNextState;
#if NJ_SYNCHRONIZATION_TEST_SNAPSHOT_SYNC_OBJECT
		NjMemUtils::Copy(pEntry->m_aSnapshot, sizeof(pEntry->m_aSnapshot), pRWLock, sizeof(NjRWLock));
#endif // NJ_SYNCHRONIZATION_TEST_SNAPSHOT_SYNC_OBJECT

		if (!bLockSuccess)
			continue;

		if ((kRand()%10)==0)
			NjThreadsSystem::Sleep(NjSharedSyncObjectConstants::TIME_GRANULARITY);

		uIndex=kSharedData.m_kTotEntries.PostIncrement();
		pEntry=kSharedData.m_aEntries+uIndex;
		pEntry->m_uRWLock=uRWLock;
		pEntry->m_pThreadData=&kThreadData;
#if NJ_SYNCHRONIZATION_TEST_SNAPSHOT_SYNC_OBJECT
		NjMemUtils::Copy(pEntry->m_aSnapshot, sizeof(pEntry->m_aSnapshot), pRWLock, sizeof(NjRWLock));
#endif // NJ_SYNCHRONIZATION_TEST_SNAPSHOT_SYNC_OBJECT

		if (uAction<=1)
		{
			pEntry->m_eLockState=RWLockFunctionalSharedData::UNLOCK_WRITE_START;
			pRWLock->UnlockWrite();
			eNextState=RWLockFunctionalSharedData::UNLOCK_WRITE_END;
		}
		else
		{
			pEntry->m_eLockState=RWLockFunctionalSharedData::UNLOCK_READ_START;
			pRWLock->UnlockRead();
			eNextState=RWLockFunctionalSharedData::UNLOCK_READ_END;
		}

		uIndex=kSharedData.m_kTotEntries.PostIncrement();
		pEntry=kSharedData.m_aEntries+uIndex;
		pEntry->m_uRWLock=uRWLock;
		pEntry->m_pThreadData=&kThreadData;
		pEntry->m_eLockState=eNextState;
#if NJ_SYNCHRONIZATION_TEST_SNAPSHOT_SYNC_OBJECT
		NjMemUtils::Copy(pEntry->m_aSnapshot, sizeof(pEntry->m_aSnapshot), pRWLock, sizeof(NjRWLock));
#endif // NJ_SYNCHRONIZATION_TEST_SNAPSHOT_SYNC_OBJECT
	}

	kThreadData.m_eErrorState=STATUS_SUCCESS;
}
//----------------------------------------------------------------------------
void NjRWLockTest::Static::RunRWLockAssertThread(void * pData)
{
	NjRWLock * const pSyncObject(reinterpret_cast<NjRWLock *>(pData));
	pSyncObject->TryLockWrite(NjSharedSyncObjectConstants::TIME_GRANULARITY*2);
}
//----------------------------------------------------------------------------
